﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Forms;
using System.Security.Principal;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Runtime.Serialization;
using System.Net;
using System.IO;

namespace JOthello_SRV
{
    class Program
    {
        private static ServiceHost srv = null;
        static void Main(string[] args)
        {

            try
            {

                bool tryRestartAsAdmin = true;
                bool isIpMode = false;

                #region check arguments
                try
                {
                    if (args.Count() > 0)
                    {
                        Output.Verbose = args.Contains("-v");
                        for (int i = 0; i < args.Count(); i++)
                        {
                            string arg = args[i];

                            switch (arg)
                            {
                                #region help
                                case "-h":
                                case "-?":
                                case "--help":
                                    printHelp(Application.ExecutablePath);
                                    return;
                                #endregion
                                #region port
                                case "-p":
                                case "--port":
                                    {
                                        int port;
                                        if (int.TryParse(args[++i], out port))
                                        {
                                            if (Properties.Settings.Default.Port != port)
                                            {
                                                Properties.Settings.Default.Port = port;
                                                Properties.Settings.Default.Save();
                                            }
                                        }
                                        else
                                        {
                                            throw new Exception("Port ungültig");
                                        }
                                    }
                                    break;
                                #endregion
                                #region try-not-restart-admin
                                case "--try-not-restart-admin":
                                    {
                                        tryRestartAsAdmin = false;
                                    }
                                    break;
                                #endregion
                                #region logging
                                case "-l":
                                case "--log":
                                    Output.LogDir = args[++i];
                                    break;
                                #endregion
                                #region mode
                                case "-m":
                                case "--mode":
                                    {
                                        i++;
                                        if (args[i].ToLower() == "ip")
                                            isIpMode = true;
                                        else if (args[i].ToLower() == "dns")
                                            isIpMode = false;
                                        else
                                            throw new Exception("Argument Mode ungueltig: -m [ip|dns]");
                                    }
                                    break;
                                #endregion
                            }
                        }
                    }
                }
                catch (Exception)
                {
                    Output.WriteError("Argumente ungültig :(");
                    printHelp(Application.ExecutablePath);
                    return;
                }
                #endregion

                #region check admin rights
                if (restartAsAdmin(args, tryRestartAsAdmin))
                    return;
                #endregion

                #region startup blacklist
                bool startupBlacklistChanged = false;
                if (Properties.Settings.Default.BlacklistIP == null)
                {
                    Properties.Settings.Default.BlacklistIP = new System.Collections.Specialized.StringCollection();
                    startupBlacklistChanged = true;
                }
                if (Properties.Settings.Default.BlacklistNames == null)
                {
                    Properties.Settings.Default.BlacklistNames = new System.Collections.Specialized.StringCollection();
                    startupBlacklistChanged = true;
                }
                if (startupBlacklistChanged) Properties.Settings.Default.Save();
                #endregion

                Output.WriteLine("Server starten...", false);

                #region startup server
                if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
                {
                    Output.WriteError("Keine aktive Internetverbindung gefunden :(");
                    return;
                }

                srv = new ServiceHost(typeof(SRVManager));
                NetTcpBinding binding = new NetTcpBinding();
                binding.Security.Mode = SecurityMode.None;
                binding.MaxReceivedMessageSize =
                    binding.ReaderQuotas.MaxArrayLength =
                    binding.ReaderQuotas.MaxBytesPerRead =
                        Int32.MaxValue;

                string endPointString = "";
                if (isIpMode)
                {
                    foreach (IPAddress ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
                    {
                        if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) continue;
                        srv.AddServiceEndpoint(typeof(Common.Interfaces.IJOthello), binding, "net.tcp://" + ip.ToString() + ":" + Properties.Settings.Default.Port.ToString() + "/JOthello");
                        endPointString += "\t" + ip.ToString() + ":" + Properties.Settings.Default.Port.ToString() + Environment.NewLine;
                    }
                }
                else
                {
                    srv.AddServiceEndpoint(typeof(Common.Interfaces.IJOthello), binding, "net.tcp://" + Dns.GetHostName() + ":" + Properties.Settings.Default.Port.ToString() + "/JOthello");
                    endPointString += "\t" + Dns.GetHostName() + ":" + Properties.Settings.Default.Port.ToString();
                }
                srv.Open();

                Output.WriteLine("Server erfolgreich gestartet...", false);
                Output.WriteLine("Endpunkte:" + Environment.NewLine + endPointString, false);
                Output.WriteName();
                #endregion

                #region main logic
                string s = "";
                do
                {
                    switch (s.ToLower())
                    {
                        #region help
                        case "help":
                        case "h":
                        case "?":
                            Output.WriteLine(Environment.NewLine + "Help:" + Environment.NewLine + Environment.NewLine
                                                + "quit".PadRight(20, ' ') + "Beendet den Server" + Environment.NewLine
                                                + "verbose".PadRight(20, ' ') + "Ändert den Modus auf verbose bzw zurück" + Environment.NewLine
                                                + "restart".PadRight(20, ' ') + "Startet den Server neu" + Environment.NewLine
                                                + "info".PadRight(20, ' ') + "Zeigt informationen an" + Environment.NewLine
                                                + "list spieler".PadRight(20, ' ') + "Listet alle registrierten Spieler auf" + Environment.NewLine
                                                + "blacklist add".PadRight(20, ' ') + "Fügt jmd. zur Blacklist hinzu" + Environment.NewLine
                                                + "blacklist remove".PadRight(20, ' ') + "Entfernt jmd. aus der Blacklist" + Environment.NewLine
                                                + "blacklist show".PadRight(20, ' ') + "Zeigt die Blacklist an" + Environment.NewLine
                                                + "kick".PadRight(20, ' ') + "Kickt einen Spieler" + Environment.NewLine
                                                + "clear".PadRight(20, ' ') + "Löscht den Konsoleninhalt");
                            break;
                        #endregion
                        #region kick
                        case "kick":
                            {
                                Output.Write("Name: ");
                                string name = Console.ReadLine();
                                if (!SRVManager.kick(name)) Output.WriteLine("Spieler " + name + " wurde nicht gefunden.", false);
                                Output.WriteName();
                            }
                            break;
                        #endregion
                        #region  blacklist remove
                        case "blacklist remove":
                            {
                                Output.Write("IP(0) oder Name(1): ");
                                if (Console.ReadLine() == "0")
                                {
                                    string ip = readIP();
                                    if (ip != null)
                                    {
                                        Properties.Settings.Default.BlacklistIP.Remove(ip);
                                        Properties.Settings.Default.Save();
                                        Output.WriteLine("Server muss neu gestartet werden, dass Änderungen wirdsam werden.", false);
                                    }
                                }
                                else
                                {
                                    Output.Write("Name: ");
                                    string name = Console.ReadLine();
                                    Properties.Settings.Default.BlacklistNames.Remove(name);
                                    SRVManager.kick(name);
                                }
                                Output.WriteName();
                            }
                            break;
                        #endregion
                        #region  blacklist add
                        case "blacklist add":
                            Output.Write("IP(0) oder Name(1): ");
                            if (Console.ReadLine() == "0")
                            {
                                string ip = readIP();
                                if (ip != null)
                                {
                                    Properties.Settings.Default.BlacklistIP.Add(ip);
                                    Properties.Settings.Default.Save();
                                }
                            }
                            else
                            {
                                Output.Write("Name: ");
                                Properties.Settings.Default.BlacklistNames.Add(Console.ReadLine());
                            }
                            Output.WriteName();
                            break;
                        #endregion
                        #region  blacklist show
                        case "blacklist show":
                            {
                                Output.WriteLine("Blacklist:", false);
                                Output.WriteLine("IPs:", false);
                                foreach (string ip in Properties.Settings.Default.BlacklistIP)
                                {
                                    Output.WriteLine("\t" + ip, false);
                                }
                                Output.WriteLine(Environment.NewLine + "Names:", false);
                                foreach (string name in Properties.Settings.Default.BlacklistNames)
                                {
                                    Output.WriteLine("\t" + name);
                                }
                                Output.WriteLine("");
                            }
                            break;
                        #endregion
                        #region info
                        case "i":
                        case "info":
                            Output.WriteLine("Server läeuft auf:" + Environment.NewLine + endPointString);
                            break;
                        #endregion
                        #region list spieler
                        case "ls":
                        case "list spieler":
                            Output.WriteLine("Registrierte Spieler:", false);
                            SRVManager.getRegisteredSpieler().ForEach((sp) => Output.WriteLine(Environment.NewLine + sp.ToString(true), false));
                            Output.WriteName();
                            break;
                        #endregion
                        #region verbose
                        case "v":
                        case "verbose":
                            Output.Verbose = !Output.Verbose;
                            Output.WriteLine("Verbose " + (Output.Verbose ? "eingeschaltet" : "ausgeschaltet") + ".");
                            break;
                        #endregion
                        #region restart
                        case "r":
                        case "restart":
                            Output.WriteLine("Server beenden...", false);
                            closeSRV();
                            Output.WriteLine("Server beendet", false);
                            Output.WriteLine("Server neu starten...", false);
                            restart(args);
                            Output.WriteLine("Server neu gestartet...", false);
                            return;
                        #endregion
                        #region clear
                        case "c":
                        case "clear":
                            Console.Clear();
                            Output.WriteName();
                            break;
                        #endregion
                        default:
                            if (s.Length > 0)
                                Output.WriteLine(Environment.NewLine + "Kein Kommando mit dem Namen \"" + s + "\" gefunden :(");
                            break;
                    }
                } while ((s = Console.ReadLine()).ToLower() != "quit" && s.ToLower() != "q" && s.ToLower() != "exit");
                #endregion

                #region closing server
                Output.WriteLine("Server beenden...", false);
                closeSRV();
                Output.WriteLine("Server beendet", false);
                #endregion
            }
            #region exceptions
            catch (Exception e)
            {
                if (e is AddressAccessDeniedException)
                {
                    Output.WriteError("Es konnte nicht auf die Serveradresse zugegriffen werden...");
                }
                else if (e is AddressAlreadyInUseException)
                {
                    Output.WriteError("Die Adresse scheint bereits in benutzung zu sein...");
                }
                else
                {
                    Output.WriteError(e.Message);
                }
                Output.WriteError("Der Server konnte nicht gestartet werden :(");
                Console.ReadKey();
            }
            #endregion
            finally
            {
                closeSRV();
            }
        }

        /// <summary>
        /// liest eine ip aus der konsole, giebt null zurück, falls der user abgebrochen hat
        /// </summary>
        /// <returns></returns>
        private static string readIP()
        {
            IPAddress ipAdd = null;
            Output.Write("IP: ");
            string ip = Console.ReadLine();
            if (!IPAddress.TryParse(ip, out ipAdd))
            {
                Output.WriteLine("IPAddresse scheint ungültig zu sein.", false);
                return null;
            }
            return ipAdd.ToString();
        }

        /// <summary>
        /// beendet den server service
        /// </summary>
        private static void closeSRV()
        {
            try
            {
                srv.Close();
            }
            catch (Exception) { }
        }

        /// <summary>
        /// giebt eine hilfe aus
        /// </summary>
        /// <param name="launchPath"></param>
        private static void printHelp(string launchPath)
        {
            Output.WriteLine(launchPath + " [-v] [-p 9552] [-l ./Loggs] [-m dns|ip] " + Environment.NewLine
                                        + "\t-v\tVerbose (Giebt mehr Informationen aus)" + Environment.NewLine
                                        + "\t-p\tPort, auf welchem der Server laufen soll" + Environment.NewLine
                                        + "\t-l\tOrdner für Logfiles" + Environment.NewLine
                                        + "\t-m\tMode (ip oder dns)" + Environment.NewLine
                                        + "\t-?\tGiebt diese Hilfe aus", false);
        }

        /// <summary>
        /// checkt ob der prozess als admin ausgeführt wird, falls dies nicht der fall ist wird das programm als admin (allenfalls erscheint ein uac fenster) neu gestartet.
        /// </summary>
        /// <param name="args"></param>
        /// <returns>geibt zurück ob das programm neu gestartet wurden musste</returns>
        private static bool restartAsAdmin(string[] args, bool tryToRestart)
        {
            WindowsPrincipal pricipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
            bool hasAdministrativeRight = pricipal.IsInRole(WindowsBuiltInRole.Administrator);
            if (!hasAdministrativeRight)
            {
                try
                {
                    Output.WriteWarning("Keine Admin-Rechte :(", false);
                    if (tryToRestart)
                    {
                        Output.WriteWarning("Server mit Admin-Rechten neu starten...", false);
                        List<String> argumente = args.ToList();
                        argumente.Add("--try-not-restart-admin");
                        restart(argumente.ToArray());
                    }
                    else
                    {
                        Output.WriteError("Server kann ohne Admin-Rechte nicht gestartet werden...");
                    }
                }
                catch (Win32Exception)
                {
                    Output.WriteError("Server kann ohne Admin-Rechte nicht gestartet werden...");
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// startet den server mit den angegebenen argumenten neu
        /// </summary>
        /// <param name="args"></param>
        private static void restart(string[] args)
        {
            ProcessStartInfo processInfo = new ProcessStartInfo();
            processInfo.Verb = "runas";
            processInfo.FileName = Application.ExecutablePath;
            processInfo.Arguments = "";
            foreach (string s in args)
            {
                processInfo.Arguments += " " + s;
            }
            Process.Start(processInfo);
        }
    }
}
